<?php

/**
 * AliPay 异步通知
 * ============================================================================
 * Author: 暴走的西瓜
 */

namespace plugin\admin\app\common\alipay;

use support\exception\BusinessException;

class Notify
{
    protected $charset;
    protected $alipayPublicKey;

    public function __construct()
    {
        $this->charset = 'utf8';
    }

    public function setAlipayPublicKey($alipayPublicKey)
    {
        $this->alipayPublicKey = $alipayPublicKey;
    }

    /**
     * 验证签名
     **/
    public function rsaCheck($params)
    {
        // 检查必要参数是否存在
        if (!isset($params['sign'], $params['sign_type'])) {
            throw new BusinessException('缺少签名或签名类型参数');
        }

        $sign = $params['sign'];
        $signType = $params['sign_type'];

        // 检查签名类型是否支持
        if (!in_array($signType, ['RSA', 'RSA2'])) {
            throw new BusinessException('不支持的签名类型: ' . $signType);
        }

        unset($params['sign_type'], $params['sign']);
        return $this->verify($this->getSignContent($params), $sign, $signType);
    }

    protected function verify($data, $sign, $signType = 'RSA')
    {
        if (empty($this->alipayPublicKey)) {
            throw new BusinessException('支付宝公钥未设置，请先调用setAlipayPublicKey方法');
        }

        // 构建标准格式的公钥
        $formattedKey = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($this->alipayPublicKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        // 验证公钥格式是否有效
        $publicKeyResource = openssl_pkey_get_public($formattedKey);
        if ($publicKeyResource === false) {
            $error = openssl_error_string();
            throw new BusinessException("支付宝RSA公钥无效: " . ($error ?: '请检查公钥格式是否正确'));
        }

        // 选择合适的加密算法
        $algorithm = ($signType === 'RSA2') ? OPENSSL_ALGO_SHA256 : OPENSSL_ALGO_SHA1;

        // 验证签名
        $result = openssl_verify($data, base64_decode($sign), $publicKeyResource, $algorithm);

        // 移除已废弃的openssl_free_key()调用，PHP 8.0+会自动管理资源
        // 释放资源
//        openssl_free_key($publicKeyResource);

        if ($result === -1) {
            $errors = [];
            while ($error = openssl_error_string()) {
                $errors[] = $error;
            }
            throw new BusinessException("签名验证失败: " . implode('; ', $errors));
        }
        return $result === 1;
    }

    public function getSignContent($params)
    {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                // 转换成目标字符集
                $v = $this->characet($v, $this->charset);
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }
        unset ($k, $v);
        return $stringToBeSigned;
    }

    /**
     * 校验 $value 是否非空
     * if not set, return true;
     * if is null, return true;
     **/
    public function checkEmpty($value)
    {
        if (!isset($value)) return true;
        if ($value === null) return true;
        if (trim($value) === "") return true;
        return false;
    }

    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    public function characet($data, $targetCharset)
    {
        if (!empty($data)) {
            $fileType = $this->charset;
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset, $fileType);
            }
        }
        return $data;
    }
}
